// --- Copyright (c) 2006-2008 Stefan Kyntchev ---
// This software is written and copyrighted by Stefan Kyntchev 
// and BeyondPod Team members. All rights are reserved.
// Author contact: support@beyondpod.mobi
// ------------------------------------------------------------------------
// This file is part of BeyondPod RSS Feed Reader and Podcast manager
// (www.codeplex.com/BeyondPod) 
// BeyondPod is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// BeyondPod is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
//  
// You should have received a copy of the GNU General Public License
// along with BeyondPod. If not, see <http://www.gnu.org/licenses/>
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.IO;
using System.Net;
using svs.Mobile.BeyondMedia.FeedCore.Helpers;
using svs.Mobile.BeyondMedia.FeedCore.RSS.Aggregators;

namespace svs.Mobile.BeyondMedia.FeedCore
{
	public enum AllowDeletions
	{
		Allow,
		DoNotAllow,
		UseParentSetting
	}

	public enum FeedViewType
	{
		Auto,
		Tracks,
		RSS
	}

	public enum FeedType
	{
		Unknown = 0,
		LocalFolder,
		RSSFeed,
		RSSFeedWithPodcasts,
		OPMLFeed
	}

	public enum PlayerIntegration
	{
		UseDefault = -1,
		UseInternalPlayer = 0,
		UseExternalPlayerTrack = 1,
		UseExternalPlayerPlaylistPLS = 2,
		UseExternalPlayerPlaylistASX = 3
	}

	public class Feed
	{
		private readonly FeedList m_Feeds = new FeedList();
		private readonly Feed m_ParentFeed;
		private readonly TrackList m_Tracks = new TrackList();
		private AllowDeletions m_AllowAutoTrackDeletions = AllowDeletions.UseParentSetting;
		private FeedCategories m_Category = new FeedCategories();
		private bool m_DownloadFailed = false;
		private Guid m_FeedID = Guid.Empty;
		private FeedType m_FeedType = FeedType.Unknown;
		private Uri m_FeedUri;
		private string m_FeedUrl;
		private string m_FeedImageUrl;
		private bool m_ForceUniqueNames = false;
		private bool m_LeftTruncateLongTrackNames = false;
		private bool m_HasNewContent = false, m_HasUnreadItems = false;
		private bool m_IncludeInOneStepDownload = true;
		private TimeSpan? m_KeepEnclosuresDuration;
		private DateTime? m_LastModifiedDate = null;
		private DateTime? m_LastServerModifiedDate = null;
		private int? m_LastItemID = null;
		private int? m_MaxEnclosures;
		private int? m_MaxEnclosuresToDownload;
		private bool m_Modified = false;
		private string m_Name;
		private int m_NumDownloadedEnclosures = 0;
		private int m_NumEnclosuresAvailableForDownload = 0;
		private string m_Path;
		private CoreHelper.SortOrder m_TrackSortOrder = CoreHelper.SortOrder.Unknown;
		private bool m_UpdateFailed = false;
		private FeedViewType m_ViewType = FeedViewType.Auto;
		private bool m_ForceFeedItemSort = false;
		private PlayerIntegration m_FeedPlayer = PlayerIntegration.UseDefault;
		private long m_UpdateSize;
		private long m_UpdatedPortion;
		private string m_UserName;
		private string m_Password;
		private int? m_NumToIncludeInWhatIsNew;

		#region Events

		public event CoreHelper.FeedChangedEvent FeedChanged;

		#endregion

		#region Constructors

		public Feed(DirectoryInfo i_directory, Feed i_Parent) : this(i_Parent)
		{
			m_Path = i_directory.FullName;
		}

		public Feed(Guid i_FeedID, Feed i_Parent) : this(i_Parent)
		{
			m_FeedID = i_FeedID;
		}

		private Feed(Feed i_Parent)
		{
			m_ParentFeed = i_Parent;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// An enclosure can be deleted if it is older than this duration (counted from now)
		/// </summary>
		public TimeSpan KeepEnclosuresDuration
		{
			get
			{
				 return m_KeepEnclosuresDuration ?? Configuration.GlobalKeepEnclosuresDuration;
			}
			set
			{
				if (m_KeepEnclosuresDuration == Configuration.GlobalKeepEnclosuresDuration)
					m_KeepEnclosuresDuration = null; // set to global default

				if (m_KeepEnclosuresDuration != value)
				{
					m_KeepEnclosuresDuration = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// Maximum nuber of enclosures that should be kept for this feed
		/// </summary>
		public int MaxEnclosures
		{
			get
			{
				 return m_MaxEnclosures ?? Configuration.GlobalMaxEnclosures;
			}
			set
			{
				if (m_MaxEnclosures == Configuration.GlobalMaxEnclosures)
					m_MaxEnclosures = null; // set to global default

				if (m_MaxEnclosures != value)
				{
					m_MaxEnclosures = value;
					MarkModified();
				}
			}
		}

		public Guid ID
		{
			get
			{
				if (m_FeedID == Guid.Empty)
				{
					m_FeedID = Guid.NewGuid();
					MarkModified();
				}
				return m_FeedID;
			}
		}

		public TrackList Tracks
		{
			get { return m_Tracks; }
		}

		public FeedList Feeds
		{
			get { return m_Feeds; }
		}

		public Feed ParentFeed
		{
			get { return m_ParentFeed; }
		}

		/// <summary>
		/// Number of unplayed tracks including tracks in child feeds
		/// </summary>
		public int UnplayedTrackCount
		{
			get
			{
				if (Feeds.Count > 0)
					return Feeds.UnplayedTrackCount;
				else
					return Tracks.UnplayedTrackCount;
			}
		}

		/// <summary>
		/// The path on the file system for the current feed
		/// </summary>
		public string FeedPath
		{
			get
			{
				if (string.IsNullOrEmpty(m_Path))
					return m_Path;

				if (m_Path.StartsWith(@"\"))
					return m_Path;
				else
					return Path.Combine(Configuration.EnclosureDownloadPath, m_Path);
			}
			set
			{
				string val = value;
				string basepath = Configuration.EnclosureDownloadPath + @"\";
				if (val.StartsWith(basepath, StringComparison.InvariantCultureIgnoreCase))
				{
					val = val.Substring(basepath.Length);
				}

				if (m_Path != val)
				{
					m_Path = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// User friendly name of the feed
		/// </summary>
		public string Name
		{
			get
			{
				if (m_Name == null)
					if (FeedPath != null)
						return Path.GetFileName(FeedPath).Replace("_", " ");
					else
						return string.Empty;
				else
					return m_Name;
			}

			set
			{
				if (m_Name != value)
				{
					m_Name = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// True if the feed exists on the files system
		/// </summary>
		public bool DownloadPathExists
		{
			get { return FeedPath != null && Directory.Exists(FeedPath); }
		}

		/// <summary>
		/// True if the tracks can be deleted from this feed. Chack the parent hierarchy
		/// if AllowTrackDeletions is set to Use Parent
		/// </summary>
		public bool CanDeleteTracks
		{
			get
			{
				switch (AllowAutoTrackDeletions)
				{
					case AllowDeletions.Allow:
						return true;

					case AllowDeletions.DoNotAllow:
						return false;

					case AllowDeletions.UseParentSetting:
						if (m_ParentFeed != null)
							return m_ParentFeed.CanDeleteTracks;
						break;
				}
				return true;
			}
		}

		/// <summary>
		/// An URL that may be associated with the feed
		/// </summary>
		public string FeedUrl
		{
			get { return m_FeedUrl; }
			set
			{
				if (m_FeedUrl != value)
				{
					MarkModified();
					m_FeedUrl = value;
					m_FeedUri = null;
				}
			}
		}

		/// <summary>
		/// An URL that may be associated with the feed. Same as FeedUrl, but in a form of URI
		/// </summary>
		public Uri FeedUri
		{
			get
			{
				if (m_FeedUri == null)
					m_FeedUri = CoreHelper.SafeCreateUri(m_FeedUrl);

				return m_FeedUri;
			}
		}

		/// <summary>
		/// An URL of an image that may be associated with the feed
		/// </summary>
		public string FeedImageUrl
		{
			get
			{
				return m_FeedImageUrl;
			}
			set
			{
				if (m_FeedImageUrl != value)
				{
					MarkModified();
					m_FeedImageUrl = value;
				}
			}
		}

		/// <summary>
		/// Sets if automatic deletions (like at the end of the track play) are allowed
		/// </summary>
		public AllowDeletions AllowAutoTrackDeletions
		{
			get
			{
				//The root feed uses the Configuration Setting
				if (Equals(FeedRepository.RootFeed))
					m_AllowAutoTrackDeletions = Configuration.AllowAutoTrackDeletions? AllowDeletions.Allow : AllowDeletions.DoNotAllow;

				return m_AllowAutoTrackDeletions;
			}
			set
			{
				m_AllowAutoTrackDeletions = value;
				MarkModified();
			}
		}

		/// <summary>
		/// Specifies that podcasts for this feed should be generated
		/// based on the URI hashcode, and not byt trying to parse the URi itself
		/// </summary>
		public bool ForceUniqueTrackNames
		{
			get { return m_ForceUniqueNames; }
			set
			{
				m_ForceUniqueNames = value;
				MarkModified();
			}
		}

		/// <summary>
		/// Instruct the UI to trunkate the beginnig of the long track names when displayed
		/// </summary>
		public bool LeftTruncateLongTrackNames
		{
			get
			{
				return m_LeftTruncateLongTrackNames;
			}
			set
			{
				m_LeftTruncateLongTrackNames = value;
				MarkModified();
			}
		}

		/// <summary>
		/// Gets a value indicating whether this feed was modified.
		/// </summary>
		/// <value><c>true</c> if modified; otherwise, <c>false</c>.</value>
		public bool IsModified
		{
			get { return m_Modified || Categories.IsModified; }
		}

		/// <summary>
		/// Gets or sets the last modified date.
		/// </summary>
		/// <value>The last modified date.</value>
		public DateTime? LastModifiedDate
		{
			get { return m_LastModifiedDate; }
			set
			{
				if (m_LastModifiedDate != value)
				{
					MarkModified();
					m_LastModifiedDate = value;
				}
			}
		}

		/// <summary>
		/// Gets or sets the a unique ID identifing the last (most recent) item in the feed.
		/// </summary>
		/// <value></value>
		public int? LastItemID
		{
			get
			{
				return m_LastItemID;
			}
			set
			{
				if (m_LastItemID != value)
				{
					MarkModified();
					m_LastItemID = value;
				}
			}
		}

		/// <summary>
		/// Gets or sets the date, the feed was modified as reported by the remote server.
		/// </summary>
		/// <value>The last modified date.</value>
		public DateTime? LastServerModifiedDate
		{
			get
			{
				return m_LastServerModifiedDate;
			}
			set
			{
				if (m_LastServerModifiedDate != value)
				{
					MarkModified();
					m_LastServerModifiedDate = value;
				}
			}
		}

		/// <summary>
		/// True idf the feed is set configured to support enclosures (e.g. has a local path specified)
		/// </summary>
		public bool AllowEnclosures
		{
			get { return !string.IsNullOrEmpty(FeedPath); }
		}

		/// <summary>
		/// True if the feed has a URL configured
		/// </summary>
		public bool HasUrl
		{
			get { return !string.IsNullOrEmpty(FeedUrl); }
		}

		/// <summary>
		/// True if the feed has a valid URI configured
		/// </summary>
		public bool HasUri
		{
			get
			{
				return HasUrl && FeedUri != null;
			}
		}

		/// <summary>
		/// The desired view type of the feed
		/// </summary>
		public FeedViewType ViewType
		{
			get { return m_ViewType; }
			set
			{
				if (m_ViewType != value)
				{
					MarkModified();
					m_ViewType = value;
				}
			}
		}

		/// <summary>
		/// Describes the type of the feed (RSS, local, OPML etc.)
		/// </summary>
		public FeedType Type
		{
			get
			{
				if (m_FeedType == FeedType.OPMLFeed)
					return m_FeedType;

				//The rest can change during the feed life span
				if (HasUrl && AllowEnclosures)
				{
					return FeedType.RSSFeedWithPodcasts;
				}
				else if (!HasUrl)
				{
					return FeedType.LocalFolder;
				}
				else
				{
					return FeedType.RSSFeed;
				}
			}
			set
			{
				if (m_FeedType != value)
				{
					MarkModified();
					m_FeedType = value;
				}
			}
		}

		/// <summary>
		/// True if the feed has new content downloaded 
		/// </summary>
		public bool HasNewContent
		{
			get { return m_HasNewContent; }
			set
			{
				if (m_HasNewContent != value)
				{
					m_HasNewContent = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// True if the feed has any unread items
		/// </summary>
		public bool HasUnreadItems
		{
			get
			{
				return m_HasUnreadItems;
			}
			set
			{
				if (m_HasUnreadItems != value)
				{
					m_HasUnreadItems = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// True if the update has failed for this feed
		/// </summary>
		public bool UpdateFailed
		{
			get { return m_UpdateFailed; }
			set { m_UpdateFailed = value; }
		}

		/// <summary>
		/// True if the download has failed for this feed
		/// </summary>
		public bool DownloadFailed
		{
			get { return m_DownloadFailed; }
			set { m_DownloadFailed = value; }
		}

		/// <summary>
		/// The category of the feed. When setting category set the entire array, do not set individual items 
		/// otherwise the changes will not be saved!
		/// </summary>
		public FeedCategories Categories
		{
			get
			{
				if (m_Category.IsUnassigned)
				{
					if (ParentFeed != null && ParentFeed != FeedRepository.RootFeed)
						return ParentFeed.Categories;
				}

				return m_Category;
			}
			set
			{
				if (m_Category != value)
				{
					m_Category = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// Should this feed be included in custom update
		/// </summary>
		public bool IncludeInOneStepDownload
		{
			get { return m_IncludeInOneStepDownload; }
			set
			{
				if (m_IncludeInOneStepDownload != value)
				{
					m_IncludeInOneStepDownload = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// If set to true ensures that feed items are sorted by their published date in Descending order
		/// </summary>
		public bool ForceFeedItemSort
		{
			get
			{
				return m_ForceFeedItemSort;
			}
			set
			{
				if (m_ForceFeedItemSort != value)
				{
					m_ForceFeedItemSort = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// Maximum enclosures to download during a One Step Update
		/// </summary>
		public int MaxEnclosuresToOneStepDownload
		{
			get
			{
				return m_MaxEnclosuresToDownload ?? Configuration.GlobalMaxEnclosuresToOneStepDownload;
			}
			set
			{
				if(m_MaxEnclosuresToDownload == Configuration.GlobalMaxEnclosuresToOneStepDownload)
					m_MaxEnclosuresToDownload = null;

				if (m_MaxEnclosuresToDownload != value)
				{
					m_MaxEnclosuresToDownload = value;
					MarkModified();
				}
			}
		}

		/// <summary>
		/// Number of enclosures downloaded in the last run
		/// </summary>
		public int NumDownloadedEnclosures
		{
			get { return m_NumDownloadedEnclosures; }
			set { m_NumDownloadedEnclosures = value; }
		}

		/// <summary>
		/// Number of (new) enclosures that were avalable for download in last run
		/// </summary>
		public int NumEnclosuresAvailableForDownload
		{
			get { return m_NumEnclosuresAvailableForDownload; }
			set { m_NumEnclosuresAvailableForDownload = value; }
		}

		/// <summary>
		/// Desired sort order for the tracks. If not set uses the default track sort order
		/// </summary>
		public CoreHelper.SortOrder TrackSortOrder
		{
			get
			{
				if (m_TrackSortOrder == CoreHelper.SortOrder.Unknown)
					return Configuration.TracksSortOrder;
				else
					return m_TrackSortOrder;
			}
			set
			{
				if (m_TrackSortOrder != value)
				{
					if(value == Configuration.TracksSortOrder)
						m_TrackSortOrder = CoreHelper.SortOrder.Unknown;
					else
						m_TrackSortOrder = value;

					MarkModified();
				}
			}
		}

		/// <summary>
		/// Desired player to be used for this feed
		/// </summary>
		public PlayerIntegration CurrentFeedPlayer
		{
			get
			{
				if (IsSubFeed)
					return m_ParentFeed.CurrentFeedPlayer;

				if (FeedPlayer == PlayerIntegration.UseDefault)
					return Configuration.PlayerIntegration;
				
				return FeedPlayer;
			}
		}

		/// <summary>
		/// Desired player to be used for this feed
		/// </summary>
		public PlayerIntegration FeedPlayer
		{
			get
			{
				return m_FeedPlayer;
			}
			set
			{
				if (m_FeedPlayer != value)
				{
					m_FeedPlayer = value;
					MarkModified();
				}
			}
		}


		/// <summary>
		/// True if this is a Sub Feed (chilld) of a local feed. (Represents a subdirectory of a local feed)
		/// </summary>
		public bool IsSubFeed
		{
			get
			{
				return (ParentFeed != null && ParentFeed.ParentFeed != null) || Equals(FeedRepository.GoUpPlaceholder);
			}
		}

		public bool CanNavigateUp
		{
			get
			{
				return ParentFeed != null;
			}
		}

		/// <summary>
		/// The total size of the RSS file to be downloaded
		/// </summary>
		public long UpdateSize
		{
			get
			{
				return m_UpdateSize;
			}
			set
			{
					m_UpdateSize = value;
			}
		}

		/// <summary>
		/// The portion of the file currently Updated
		/// </summary>
		public long UpdatedPortion
		{
			get
			{
				return m_UpdatedPortion;
			}
			set
			{
					m_UpdatedPortion = value;
			}
		}

		/// <summary>
		/// Percent of file Updateed
		/// </summary>
		public int UpdatePercent
		{
			get
			{
				if (UpdatedPortion >= 0 && UpdateSize >= 0 && UpdateSize >= UpdatedPortion)
					return (int)(((decimal)UpdatedPortion / (decimal)UpdateSize) * 100);
				else
					return 0;
			}
		}

		/// <summary>
		/// User Name (if the feed is password protected)
		/// </summary>
		public string UserName
		{
			get
			{
				return m_UserName;
			}
			set
			{
				if (m_UserName == value) return;

				m_UserName = value;
				MarkModified();
			}
		}

		/// <summary>
		/// Password (if the feed is password protected)
		/// </summary>
		public string Password
		{
			get
			{
				return m_Password;
			}
			set
			{
				if (m_Password == value)
					return;

				m_Password = value;
				MarkModified();
			}
		}

		public NetworkCredential Credentials
		{
			get
			{
				return !string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password)
				       	? new NetworkCredential(UserName, Password)
								: null;
			}
		}

		public bool HideReadItems { get; set; }

		public bool IsAggregate
		{
			get
			{
				return HasUri && CategoryAggregate.IsAggreagateFeed(FeedUri);
			}
		}

		/// <summary>
		/// Maximum number of unread items to be included in WhatIsNew aggregate
		/// </summary>
		public int NumToIncludeInWhatIsNew
		{
			get
			{
				return m_NumToIncludeInWhatIsNew ?? Configuration.GlobalNumItemsToIncludeInWhatIsNew;
			}
			set
			{
				if (m_NumToIncludeInWhatIsNew == Configuration.GlobalNumItemsToIncludeInWhatIsNew)
					m_NumToIncludeInWhatIsNew = null; // set to global default

				if (m_NumToIncludeInWhatIsNew != value)
				{
					m_NumToIncludeInWhatIsNew = value;
					MarkModified();
				}
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Used only byt the repository
		/// </summary>
		/// <returns></returns>
		internal string GetRawFeedPath()
		{
			return m_Path;
		}

		/// <summary>
		/// Updates the name of the feed if the name was autogenerated from the url
		/// </summary>
		/// <param name="title"></param>
		public void UpdateNameIfDefault(string title)
		{
			if ((Name == FeedUrl || string.IsNullOrEmpty(Name)) &&
			    !string.IsNullOrEmpty(title))
			{
				Name = title;
			}
		}

		public override string ToString()
		{
			return String.Format("Feed: {0}", Name);
		}

		public override bool Equals(object obj)
		{
			if (this == obj) return true;
			Feed feed = obj as Feed;
			if (feed == null) return false;
			return Equals(ID, feed.ID);
		}

		public override int GetHashCode()
		{
			return m_FeedID.GetHashCode();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Marks a track as modified
		/// </summary>
		private void MarkModified()
		{
			m_Modified = true;
			if (FeedChanged != null)
				FeedChanged(this);
		}

		#endregion
	}

	
}